/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.webanovare.ruzzle.game;

import com.webanovare.ruzzle.misc.Trie;
import com.webanovare.ruzzle.misc.TrieManager;
import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Random;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * creates a Trie tree of a wordlist.
 * Also makes sure that the given entry for a word is correct 
 * according to the Trie tree.
 */
public class Ruzzle {

    final private NavigableMap<Integer, Character> charFrequencies = new TreeMap<Integer, Character>();
    final private Map<Character, Integer> charPoints = new HashMap<Character, Integer>();
    private int total;

    private Character[][] gameArea;
    private int size;
    private Trie wordList;

    public Ruzzle(int size, File wordList, File letterProperties) {
        this.gameArea = new Character[size][size];
        this.size = size;
        try {
            this.wordList = TrieManager.initTrie(wordList);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Ruzzle.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.initLetters(letterProperties);
        this.generateGameArea();
    }

    private void initLetters(File f) {
        Scanner sc = null;
        try {
            sc = new Scanner(f);
            while (sc.hasNextLine()) {
                String s = sc.nextLine();
                String[] representation = s.split(",");
                char letter = representation[0].charAt(0);
                int freq = Integer.parseInt(representation[1]);
                int point = Integer.parseInt(representation[2]);
                this.total += freq;
                this.charFrequencies.put(total, letter);
                this.charPoints.put(letter, point);
            }
        } catch (IOException e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "File not found: " + e.getMessage());
            try {
                Logger.getAnonymousLogger().log(Level.SEVERE, "File not found: " + new File( "." ).getCanonicalPath().toString());
            } catch (IOException ex) {
                Logger.getLogger(Ruzzle.class.getName()).log(Level.SEVERE, null, ex);
            }
        } finally {
            if (sc != null) {
                sc.close();
            }
        }
    }

    private void generateGameArea() {
        for (int i = 0; i < this.size; i++) {
            for (int j = 0; j < this.size; j++) {
                Random r = new Random();
                System.out.println("total " + this.total);
                int seed = r.nextInt(this.total);
                this.gameArea[i][j] = this.charFrequencies.higherEntry(seed).getValue();
            }
        }
    }
    
    public int getSize() {
        return this.size;
    }

    /**
     * Checks if the given list of points is a valid entry on the game area.
     * Following things will be checked;
     *  * Whether the entry is a sequence of neighbors.
     *  * Whether the entry is a valid entry in the dictionary
     * @param ps    List of coordinates the player has played.
     * @return      True if the entry was valid, otherwise false.
     */
    public boolean validEntry(List<Point> ps) {
        if(ps == null || ps.isEmpty()) {
            return false;
        }
        
        for(int i = 0; i < ps.size()-1; i++) {
            if(i == ps.size()) {
                break;
            } 
            
            Point p1 = ps.get(i);
            Point p2 = ps.get(i+1);
            
            if(!this.isNeighbours(p1, p2)) {
                return false;
            }
        }
        
        if(!this.wordList.checkWord(this.getWord(ps))) {
            return false;
        }
        
        return true;
    }
    
    private boolean withinBounds(Point p) {
        return p.x >= 0 && p.x < this.size && p.y >= 0 && p.y < this.size;
    }
    
    private boolean isNeighbours(Point p1, Point p2) {
        if(!(this.withinBounds(p1) || this.withinBounds(p2))) {
            return false;
        }
        int diffx = p1.x-p2.x;
        int diffy = p1.y-p2.y;
        
        if(diffx == 0 && diffy == 0) {
            return false;
        }
        
        return (diffx == -1 || diffx == 0 || diffx == 1) && 
               (diffy == -1 || diffy == 0 || diffy == 1);
    }

    /**
     * Returns the word corresponding to the list of coordinates. This is
     * dependent on the view of the game area.
     * @param ps
     * @return The corresponding word.
     */
    public String getWord(List<Point> ps) {
        StringBuilder b = new StringBuilder();
        for (Point p : ps) {
            b.append(this.gameArea[p.y][p.x]);
        }
        return b.toString();
    }

    /**
     * Calculates the total amount of points given by a word, according to the
     * point table.
     * @param word
     * @return 
     */
    public int calculatePoints(String word) {
        int points = 0;
        for (char c : word.toCharArray()) {
            points += this.charPoints.get(c);
        }
        return points;
    }

    /**
     * Returns a certain point corresponding to the given character.
     * @param c
     * @return 
     */
    public int getPoint(char c) {
        return this.charPoints.get(this.validChar(c));
    }
    
    public List<Character> getBoard() {
        List<Character> board = new LinkedList<Character>();
        for(int i = 0; i < this.size; i++) {
            for(int j = 0; j < this.size; j++) {
                board.add(this.gameArea[i][j]);
            }
        }
        return board;
    }
    
    public Character[][] getBoardMatrix() {
        return this.gameArea;
    }
    
    public Trie getTrie() {
        return this.wordList;
    }

    private char validChar(char c) {
        return Character.toUpperCase(c);
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < this.size; i++) {
            for (int j = 0; j < this.size; j++) {
                b.append(this.gameArea[i][j]).append(" ");
            }
            b.append("\n");
        }

        return b.toString();
    }
}
